using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;

namespace Beaker.OpenCube.Render.Maths
{
	public struct DisplayColor
	{
		private readonly int _color;

		public DisplayColor( byte r, byte g, byte b )
		{
			_color = ( r << 16 ) | ( g << 8 ) | b;
		}

		public byte R
		{
			get { return (byte)( ( _color >> 16 ) & 0xff ); }
		}

		public byte G
		{
			get { return (byte)( ( _color >> 8 ) & 0xff ); }
		}

		public byte B
		{
			get { return (byte)( _color & 0xff ); }
		}
	}

	[DebuggerDisplay( "({Debug})" )]
	public struct Color
		: IEquatable<Color>
	{
		public static readonly Color White = new Color( 1d, 1d, 1d );
		public static readonly Color Gray = new Color( 0.5d, 0.5d, 0.5d );
		public static readonly Color Black = new Color( 0d, 0d, 0d );
		public static readonly Color Background = Black;
		public static readonly Color DefaultColor = Black;

		private readonly double _r;
		private readonly double _g;
		private readonly double _b;

		public Color( double r, double g, double b )
		{
			Contract.Requires( r >= 0d );
			Contract.Requires( g >= 0d );
			Contract.Requires( b >= 0d );

			_r = r;
			_g = g;
			_b = b;
		}

		[Pure]
		public double R
		{

			get
			{
				Contract.Ensures( Contract.Result<double>() >= 0d );
				return _r;
			}
		}

		[Pure]
		public double G
		{

			get
			{
				Contract.Ensures( Contract.Result<double>() >= 0d );
				return _g;
			}
		}

		[Pure]
		public double B
		{

			get
			{
				Contract.Ensures( Contract.Result<double>() >= 0d );
				return _b;
			}
		}

#if DEBUG
		public string Debug
		{
			get
			{
				DisplayColor rgb = ToDisplayColor();
				return string.Format( "0x{0:X2}{1:X2}{2:X2}", rgb.R, rgb.G, rgb.B );
			}
		}

#endif

		public DisplayColor ToDisplayColor()
		{
			Func<double, byte> convert = value => value > 1d ? (byte)255 : (byte)Math.Floor( value * 255 );
			return new DisplayColor( convert( _r ), convert( _g ), convert( _b ) );
		}

		#region Addition

		[Pure]
		public Color Add( Color other )
		{
			return new Color( _r + other._r, _g + other._g, _b + other._b );
		}

		[Pure]
		public static Color Add( Color left, Color right )
		{
			return left.Add( right );
		}

		[Pure]
		public static Color operator +( Color left, Color right )
		{
			return left.Add( right );
		}

		#endregion
		#region Multiplication

		[Pure]

		public Color Multiply( double s )
		{
			return new Color( _r * s, _g * s, _b * s );
		}

		[Pure]

		public static Color Multiply( Color left, double right )
		{
			return left.Multiply( right );
		}

		[Pure]

		public static Color operator *( Color left, double right )
		{
			return left.Multiply( right );
		}

		[Pure]

		public static Color operator *( double left, Color right )
		{
			return right.Multiply( left );
		}

		[Pure]

		public Color Multiply( Color other )
		{
			return new Color( _r * other._r, _g * other._g, _b * other._b );
		}

		[Pure]

		public static Color Multiply( Color left, Color right )
		{
			return left.Multiply( right );
		}

		[Pure]

		public static Color operator *( Color left, Color right )
		{
			return left.Multiply( right );
		}

		#endregion
		#region Division

		[Pure]

		public Color Divide( double d )
		{
			var s = 1d / d;
			return new Color( _r * s, _g * s, _b * s );
		}

		[Pure]

		public static Color Divide( Color left, double right )
		{
			return left.Divide( right );
		}

		[Pure]

		public static Color operator /( Color left, double right )
		{
			return left.Divide( right );
		}

		#endregion
		#region Powers

		[Pure]

		public Color Power2()
		{
			return
				new Color(
					_r * _r,
					_g * _g,
					_b * _b
				);
		}

		[Pure]

		public static Color Power2( Color color )
		{
			return color.Power2();
		}

		[Pure]
		public Color Power( double power )
		{
			return
				new Color(
					Math.Pow( _r, power ),
					Math.Pow( _g, power ),
					Math.Pow( _b, power )
				);
		}

		[Pure]

		public static Color Power( Color color, double power )
		{
			return color.Power( power );
		}

		#endregion
		#region Comparison

		public override bool Equals( object obj )
		{
			if( ReferenceEquals( null, obj ) ) return false;
			return obj is Color && Equals( (Color)obj );
		}

		[Pure]
		public bool Equals( Color other )
		{
			return _r.AboutEqual( other._r ) && _g.AboutEqual( other._g ) && _b.AboutEqual( other._b );
		}

		[Pure]
		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = _r.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _g.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _b.GetHashCode();
				return hashCode;
			}
		}

		[Pure]

		public static bool operator ==( Color left, Color right )
		{
			return left.Equals( right );
		}

		[Pure]

		public static bool operator !=( Color left, Color right )
		{
			return !left.Equals( right );
		}

		#endregion
	}

	public static class ColorExtensions
	{
		public static Color ToColor( this System.Drawing.Color gdiColor )
		{
			return
				new Color(
					gdiColor.R / 255d,
					gdiColor.G / 255d,
					gdiColor.B / 255d
				);
		}
	}
}